Completed
Pull Request — master (#80)
by
unknown
02:48
created

SizeEstimation.calculateOutputsSize   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
nc 1
dl 0
loc 8
rs 9.4285
nop 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A 0 4 1
1
var assert = require('assert');
2
var bitcoin = require('bitcoinjs-lib');
3
4
var SizeEstimation = {
5
    SIZE_DER_SIGNATURE: 72,
6
    SIZE_V0_P2WSH: 36
7
};
8
9
SizeEstimation.getPublicKeySize = function(isCompressed) {
10
    return isCompressed ? 33 : 65;
11
};
12
13
SizeEstimation.getLengthForScriptPush = function(length) {
14
    if (length < 75) {
15
        return 1;
16
    } else if (length <= 0xff) {
17
        return 2;
18
    } else if (length <= 0xffff) {
19
        return 3;
20
    } else if (length <= 0xffffffff) {
21
        return 5;
22
    } else {
23
        throw new Error("Size of pushdata too large");
24
    }
25
};
26
27
SizeEstimation.getLengthForVarInt = function(length) {
28
    if (length < 253) {
29
        return 1;
30
    }
31
32
    // Rest have a prefix byte
33
    var numBytes;
34
    if (length < 65535) {
35
        numBytes = 2;
36
    } else if (length < 4294967295) {
37
        numBytes = 4;
38
    } else if (length < 18446744073709551615) {
39
        numBytes = 8;
40
    } else {
41
        throw new Error("Size of varint too large");
42
    }
43
44
    return 1 + numBytes;
45
};
46
47
SizeEstimation.estimateMultisigStackSize = function(m, keys) {
48
    // Initialize with OP_0
49
    var stackSizes = [0];
50
    var i;
51
    for (i = 0; i < m; i++) {
52
        stackSizes.push(SizeEstimation.SIZE_DER_SIGNATURE);
53
    }
54
55
    var scriptSize = 1; // OP_$m
56
    for (i = 0; i < keys.length; i++) {
57
        scriptSize += this.getLengthForScriptPush(keys[i].length) + keys[i].length;
58
    }
59
    scriptSize += 2; // OP_$n OP_CHECKMULTISIG
60
    return [stackSizes, scriptSize];
61
};
62
63
SizeEstimation.estimateP2PKStackSize = function(key) {
64
    var stackSizes = [SizeEstimation.SIZE_DER_SIGNATURE];
65
    var scriptSize = this.getLengthForScriptPush(key.length) + key.length + 1; // KEY OP_CHECKSIG
66
67
    return [stackSizes, scriptSize];
68
};
69
70
SizeEstimation.estimateP2PKHStackSize = function(isCompressed) {
71
    if (typeof isCompressed === 'undefined') {
72
        isCompressed = true;
73
    }
74
75
    var stackSizes = [this.SIZE_DER_SIGNATURE, this.getPublicKeySize(isCompressed)];
76
    var scriptSize = 2 + this.getLengthForScriptPush(20) + 20 + 2;
77
78
    return [stackSizes, scriptSize];
79
};
80
81
/**
82
 * As pure a function as it gets, but without the overhead
83
 * of checking everything. Make sure your calls are correct.
84
 *
85
 * @param {Buffer} stackSizes
86
 * @param {Buffer} isWitness
87
 * @param {Buffer} rs
88
 * @param {Buffer} ws
89
 */
90
SizeEstimation.estimateStackSignatureSize = function(stackSizes, isWitness, rs, ws) {
91
    assert(ws === null || isWitness);
92
93
    var scriptSigSizes = [];
94
    var witnessSizes = [];
95
    if (isWitness) {
96
        witnessSizes = stackSizes;
97
        if (ws instanceof Buffer) {
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
98
            witnessSizes.push(ws.length);
99
        }
100
    } else {
101
        scriptSigSizes = stackSizes;
102
    }
103
104
    if (rs instanceof Buffer) {
105
        scriptSigSizes.push(rs.length);
106
    }
107
108
    var self = this;
109
    var scriptSigSize = 0;
110
    scriptSigSizes.map(function(elementLen) {
111
        scriptSigSize += self.getLengthForScriptPush(elementLen) + elementLen;
112
    });
113
114
    scriptSigSize += self.getLengthForVarInt(scriptSigSize);
115
116
    var witnessSize = 0;
117
    if (witnessSizes.length > 0) {
118
        witnessSizes.map(function(elementLen) {
119
            witnessSize += self.getLengthForVarInt(elementLen) + elementLen;
120
        });
121
        witnessSize += self.getLengthForVarInt(witnessSizes.length);
122
    }
123
124
    return [scriptSigSize, witnessSize];
125
};
126
127
/**
128
 *
129
 * @param {Buffer} script - main script, can equal RS/WS too
130
 * @param {Buffer} redeemScript
131
 * @param {Buffer} witnessScript
132
 * @param {boolean} isWitness - required, covers P2WPKH and so on
133
 * @param {boolean} compressed - only strictly required for p2pkh
134
 */
135
SizeEstimation.estimateInputFromScripts = function(script, redeemScript, witnessScript, isWitness, compressed) {
136
    assert(witnessScript === null || isWitness);
137
138
    var stackSizes;
139
    if (bitcoin.script.multisig.output.check(script)) {
140
        var multisig = bitcoin.script.multisig.output.decode(script);
141
        stackSizes = this.estimateMultisigStackSize(multisig.m, multisig.pubKeys)[0];
142
    } else if (bitcoin.script.pubKey.output.check(script)) {
143
        var p2pk = bitcoin.script.pubKey.output.decode(script);
144
        stackSizes = this.estimateP2PKStackSize(p2pk)[0];
145
    } else if (bitcoin.script.pubKeyHash.output.check(script)) {
146
        stackSizes = this.estimateP2PKHStackSize(compressed)[0];
147
    } else {
148
        throw new Error("Unsupported script type");
149
    }
150
151
    return this.estimateStackSignatureSize(stackSizes, isWitness, redeemScript, witnessScript);
152
};
153
154
SizeEstimation.estimateUtxo = function(utxo, compressed) {
155
    var spk = Buffer.from(utxo.scriptpubkey_hex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
156
    var rs = null;
157
    var ws = null;
158
159
    if (utxo.redeem_script) {
160
        if (typeof utxo.redeem_script === 'string') {
161
          rs = Buffer.from(utxo.redeem_script, 'hex')
162
        } else if (utxo.redeem_script instanceof Buffer) {
163
          rs = utxo.redeem_script;
164
        }
165
    }
166
167
    if (utxo.witness_script) {
168
        if (typeof utxo.witness_script === 'string') {
169
          ws = Buffer.from(utxo.witness_script, 'hex')
170
        } else if (utxo.witness_script instanceof Buffer) {
171
          ws = utxo.witness_script;
172
        }
173
    }
174
175
    var witness = false;
176
177
    var signScript = spk;
178
    if (bitcoin.script.scriptHash.output.check(signScript)) {
179
        if (null === rs) {
180
            throw new Error("Cant estimate, missing redeem script");
181
        }
182
        signScript = rs;
183
    }
184
185
    if (bitcoin.script.witnessPubKeyHash.output.check(signScript)) {
186
        var p2wpkh = bitcoin.script.witnessPubKeyHash.output.decode(signScript);
187
        signScript = bitcoin.script.pubKeyHash.output.encode(p2wpkh);
188
        witness = true;
189
    } else if (bitcoin.script.witnessScriptHash.output.check(signScript)) {
190
        if (null === ws) {
191
            throw new Error("Can't estimate, missing witness script");
192
        }
193
        signScript = ws;
194
        witness = true;
195
    }
196
197
    var types = bitcoin.script.types;
198
    var allowedTypes = [types.MULTISIG, types.P2PKH, types.P2PK];
199
    var type = bitcoin.script.classifyOutput(signScript);
200
    if (allowedTypes.indexOf(type) === -1) {
201
        throw new Error("Unsupported script type");
202
    }
203
204
    var estimation = this.estimateInputFromScripts(signScript, rs, ws, witness, compressed);
205
206
    return {
207
        scriptSig: estimation[0],
208
        witness: estimation[1]
209
    };
210
};
211
212
/**
213
 * Returns the size of input related data, given a set
214
 * of utxos we can estimate for. witness data is included
215
 * if withWitness=true, and is required for vsize/weight
216
 * calculations
217
 * @param {object} utxos
218
 * @param {boolean} withWitness
219
 * @returns {number}
220
 */
221
SizeEstimation.estimateInputsSize = function(utxos, withWitness) {
222
    var inputSize = 0;
223
    var witnessSize = 0;
224
    utxos.map(function(utxo) {
225
        var estimate = SizeEstimation.estimateUtxo(utxo);
226
        // txid + vout + sequence + scriptSig
227
        inputSize += 32 + 4 + 4 + estimate.scriptSig;
228
        if (withWitness) {
229
            witnessSize += estimate.witness;
230
        }
231
    });
232
233
    if (withWitness && witnessSize > 0) {
234
        inputSize += 2 + witnessSize;
235
    }
236
237
    return inputSize;
238
};
239
240
/**
241
 * Calculates number of bytes to serialize tx outputs
242
 * @param {Array} outs
243
 * @returns {number}
244
 */
245
SizeEstimation.calculateOutputsSize = function(outs) {
246
    var outputsSize = 0;
247
    outs.map(function(out) {
248
        var scriptSize = SizeEstimation.getLengthForVarInt(out.script.length);
249
        outputsSize += 8 + scriptSize + (out.script.length);
250
    });
251
    return outputsSize;
252
};
253
254
/**
255
 * Returns the transactions weight.
256
 *
257
 * @param {bitcoin.Transaction} tx
258
 * @param {array} utxos
259
 * @returns {*}
260
 */
261
SizeEstimation.estimateTxWeight = function(tx, utxos) {
262
    var outSize = SizeEstimation.calculateOutputsSize(tx.outs);
263
    // version + vinLen + vin + voutLen + vout + nlocktime
264
    var baseSize = 4 + SizeEstimation.getLengthForVarInt(utxos.length) + this.estimateInputsSize(utxos, false) +
265
        SizeEstimation.getLengthForVarInt(tx.outs.length) + outSize + 4;
266
    // version + vinLen + vin (includes witness) + voutLen + vout + nlocktime
267
    var witSize = 4 + SizeEstimation.getLengthForVarInt(utxos.length) + this.estimateInputsSize(utxos, true) +
268
        SizeEstimation.getLengthForVarInt(tx.outs.length) + outSize + 4;
269
270
    return (3 * baseSize) + witSize;
271
};
272
273
/**
274
 * Returns the vsize for a transaction. Same as size
275
 * for transaction with no witness data.
276
 *
277
 * @param {bitcoin.Transaction} tx
278
 * @param {array} utxos
279
 * @returns {Number}
280
 */
281
SizeEstimation.estimateTxVsize = function(tx, utxos) {
282
    return parseInt(Math.ceil(SizeEstimation.estimateTxWeight(tx, utxos) / 4), 10);
283
};
284
285
module.exports = SizeEstimation;
286